﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Text;
using System.Windows.Forms;
using Mersiv.App.Client.Data;
using Mersiv.App.Client.Controls;
using Mersiv.App.Client.Controller;
using Mersiv.App.Client.Logging;
using Mersiv.Lib.Wrapper;
using Mersiv.Lib.Wrapper.Command;
using Mersiv.Lib.Wrapper.Event;
using System.Drawing;

namespace Mersiv.App.Client
{
  public partial class ApplicationForm : Form
  {

    private DataModel dataModel;
    private HgPollController hgPollController;
    private HgCommandController hgCommandController;

    private List<HgRepositoryFileSystemMonitor> hgRepositoryFileSystemMonitorList;


    private Font nodeNormalFont;
    private Font nodeModifiedFont;


    public ApplicationForm()
    {
      // Make sure that logging is in place first
      LogManager.Instance.LogMessageAdded += new LogEvent(logManager_LogMessageAdded);

      InitializeComponent();

      TreeNode tempNode = new TreeNode("temp");
      this.nodeNormalFont = new Font(this.Font, FontStyle.Regular);
      this.nodeModifiedFont = new Font(this.Font, FontStyle.Bold);

      this.hgRepositoryFileSystemMonitorList = new List<HgRepositoryFileSystemMonitor>();

      this.dataModel = new DataModel();
      this.repositoryPanel.DataModel = this.dataModel;
      this.dataModel.FileName = "_IndexRepositoryList.hg";
      this.dataModel.RepositoryAdded += new HgRepositoryEvent(dataModel_RepositoryAdded);
      this.dataModel.RepositoryRemoved += new HgRepositoryEvent(dataModel_RepositoryRemoved);
      this.dataModel.Load();
      //
      // TODO: Setup the folder monitors after the dataModel is loaded
      //
      this.hgPollController = new HgPollController();
      this.hgPollController.DataModel = this.dataModel;
      this.hgCommandController = new HgCommandController();
      this.hgCommandController.CommandExecutionCompleted += new HgCommandEvent(hgCommandController_CommandExecutionCompleted);
      this.hgCommandController.CommandOutputReceived += new HgCommandEvent(hgCommandController_CommandOutputReceived);
      this.hgCommandController.CommandErrorReceived += new HgCommandEvent(hgCommandController_CommandErrorReceived);
      this.repositoryPanel.HgCommandController = this.hgCommandController;
      this.repositoryPanel.HgRepositorySelected += new HgRepositoryEvent(repositoryPanel_HgRepositorySelected);

      //
      // TODO: Load UI settings for "last state".
      //
      this.displayPanel.Dock = DockStyle.Fill;
    }

    private void repositoryPanel_HgRepositorySelected(HgRepository hgRepository)
    {
      //
      // TODO: Store the tree in memory once it is built, so that we save state
      // when switching between different repositories.
      //
      this.BuildRepositoryTree(hgRepository);

      if (this.hgCommandController != null)
      {
        this.hgCommandController.HgStatusCommand(hgRepository);
      }
    }

    private void BuildRepositoryTree(HgRepository hgRepository)
    {
      // SuspendLayout() and then ResumeLayout() to prevent UI drawing until we are done
      this.folderTreeView.SuspendLayout();
      // Clear existing tree
      this.folderTreeView.Nodes.Clear();

      // Create the base root node for the entire tree
      TreeNode rootNode = new TreeNode(hgRepository.Name);
      rootNode.ToolTipText = hgRepository.WorkingDirectory;
      rootNode.ImageIndex = 0;
      rootNode.SelectedImageIndex = 0;

      this.BuildNode(hgRepository, rootNode, hgRepository.WorkingDirectory);
      this.folderTreeView.Nodes.Add(rootNode);

      this.folderTreeView.ResumeLayout();
    }
    private void BuildNode(HgRepository hgRepository, TreeNode rootNode, string path)
    {
      DirectoryInfo directoryInfo = new DirectoryInfo(path);
      TreeNode pathNode = new TreeNode(directoryInfo.Name);
      pathNode.ToolTipText = path;
      pathNode.ImageIndex = 1;
      pathNode.SelectedImageIndex = 1;
      if (directoryInfo.Exists)
      {
        DirectoryInfo[] directoryInfoList = directoryInfo.GetDirectories();
        foreach(DirectoryInfo directoryInfoFolder in directoryInfoList)
        {
          // Ignore ".hg" folders and contents
          if (directoryInfoFolder.Name != ".hg")
          {
            this.BuildNode(hgRepository, pathNode, directoryInfoFolder.FullName);
          }
        }
        FileInfo[] files = directoryInfo.GetFiles();
        foreach (FileInfo fileInfo in files)
        {
          TreeNode fileNode = new TreeNode(fileInfo.Name);
          fileNode.ToolTipText = fileInfo.FullName;
          fileNode.Tag = fileInfo.FullName.Replace(hgRepository.WorkingDirectory + "\\", "");
          fileNode.ToolTipText = fileInfo.FullName.Replace(hgRepository.WorkingDirectory, "");
          fileNode.ImageKey = "page_green.png";
          fileNode.SelectedImageKey = "page_green.png";
          pathNode.Nodes.Add(fileNode);
        }
      }
      rootNode.ExpandAll();
      rootNode.Nodes.Add(pathNode);
    }


    private void UpdateTree(HgStatusCommand hgStatusCommand)
    {
      foreach (TreeNode treeNode in this.folderTreeView.Nodes)
      {
        UpdateTreeRecursive(hgStatusCommand, treeNode);
      }
      // NOTE: This solves a bug where bolding TreeNode text truncates the text
      //http://stackoverflow.com/questions/2272493/c-winforms-bold-treeview-node-doesnt-show-whole-text
      this.folderTreeView.BackColor = this.folderTreeView.BackColor;
    }
    private void UpdateTreeRecursive(HgStatusCommand hgStatusCommand, TreeNode rootNode)
    {
      try
      {
        if (rootNode != null)
        {
          //
          // TODO: Process Root Node
          //
          foreach (TreeNode treeNode in rootNode.Nodes)
          {
            if (treeNode != null)
            {
              foreach (HgStatusCommand.ResultCode rc in hgStatusCommand.ResultCodeList)
              {
                if (treeNode.Tag != null)
                {
                  if (treeNode.Tag.ToString() == rc.Name)
                  {
                    //
                    // TODO: Change the display properties based on the rc.Code value
                    //
                    treeNode.ImageKey = "page_red.png";
                    treeNode.SelectedImageKey = "page_red.png";
                    treeNode.NodeFont = this.nodeModifiedFont;
                    // TODO: Check if a code has already been pre-pended.
                    treeNode.Text = rc.Code + " - " + treeNode.Text;
                    TreeNode parentNode = treeNode.Parent;
                    while (parentNode != null)
                    {
                      parentNode.NodeFont = this.nodeModifiedFont;
                      parentNode = parentNode.Parent;
                    }
                    // NOTE: Break here to prevent subsequent passes from resetting the UI?
                    break;
                    //LogManager.Instance.Add("TAG: " + treeNode.Tag.ToString() + " RC: " + rc.Name);
                  }
                  else
                  {
                    //
                    // TODO: Reset?
                    //
                    treeNode.ImageKey = "page_green.png";
                    treeNode.SelectedImageKey = "page_green.png";
                    treeNode.NodeFont = this.nodeNormalFont;
                  }
                }
              }
              this.UpdateTreeRecursive(hgStatusCommand, treeNode);
            }
          }
        }
      }
      catch (Exception ex)
      {
        LogManager.Instance.Add("UpdateTreeRecursive(): " + ex.ToString());
      }
    }


    #region HgCommandController Events

    private void hgCommandController_CommandExecutionCompleted(HgCommand hgCommand, string message)
    {
      //
      // NOTE: Always allow empty strings because sometimes that has meaning.
      //
      //*/
      this.ProcessCommandExecutionCompleted(hgCommand, message);
    }

    private void ProcessCommandExecutionCompleted(HgCommand hgCommand, string message)
    {
      if (this.InvokeRequired)
      {
        try
        {
          HgCommandEvent hgCommandEvent = new HgCommandEvent(this.ProcessCommandExecutionCompleted);
          this.Invoke(hgCommandEvent, new object[] { hgCommand, message });
        }
        catch (Exception exception)
        {
          LogManager.Instance.Add("ERROR: " + exception.ToString());
        }
      }
      else
      {
        this.logTextBox.AppendText(message);
        this.logTextBox.AppendText(Environment.NewLine);

        if (!string.IsNullOrEmpty(hgCommand.Error))
        {
          this.logTextBox.AppendText(message);
          this.logTextBox.AppendText(Environment.NewLine);
        }

        //
        // TODO: A better design would be to pass in a callback of some sort to the command for the command
        // to execute on.
        //
        // EXAMPLE: Pass in the file structure tree for updating the UI.
        //

        if (hgCommand is HgStatusCommand)
        {
          HgStatusCommand hgStatusCommand = (HgStatusCommand)hgCommand;
          this.UpdateTree(hgStatusCommand);
        }
      }
    }

    private void hgCommandController_CommandOutputReceived(HgCommand hgCommand, string message)
    {
      // NOTE: Always allow empty strings because sometimes that has meaning.
      if (this.InvokeRequired)
      {
        HgCommandEvent hgCommandEvent = new HgCommandEvent(hgCommandController_CommandOutputReceived);
        this.Invoke(hgCommandEvent, new object[] { hgCommand, message });
      }
      else
      {
        this.logTextBox.AppendText(message);
        this.logTextBox.AppendText(Environment.NewLine);
      }
    }

    private void hgCommandController_CommandErrorReceived(HgCommand hgCommand, string message)
    {
      // NOTE: Always allow empty strings because sometimes that has meaning.
      if (this.InvokeRequired)
      {
        HgCommandEvent hgCommandEvent = new HgCommandEvent(hgCommandController_CommandErrorReceived);
        this.Invoke(hgCommandEvent, new object[] { hgCommand, message });
      }
      else
      {
        this.logTextBox.AppendText(message);
        this.logTextBox.AppendText(Environment.NewLine);
      }
    }

    #endregion HgCommandController Events
    


    private delegate void AppendLogMessageCallback(LogMessage logMessage);
    private void logManager_LogMessageAdded(LogMessage logMessage)
    {
      if (logMessage != null && logMessage.Message != null)
      {
        //this.logTextBox.AppendText(logMessage.Message);
        //this.logTextBox.AppendText(Environment.NewLine);
        if (this.InvokeRequired)
        {
          AppendLogMessageCallback appendLogMessageCallback = new AppendLogMessageCallback(logManager_LogMessageAdded);
          this.Invoke(appendLogMessageCallback, new object[] { logMessage });
        }
        else
        {
          this.logTextBox.AppendText(logMessage.Message);
          this.logTextBox.AppendText(Environment.NewLine);
        }
      }
    }



    private void hgRepositoryFileSystemMonitor_FileIndexUpdate(string path)
    {
      if (path != null)
      {
        if (this.InvokeRequired)
        {
          HgRepositoryFileSystemMonitor.FileIndexEvent fileIndexEvent = new HgRepositoryFileSystemMonitor.FileIndexEvent(hgRepositoryFileSystemMonitor_FileIndexUpdate);
          this.Invoke(fileIndexEvent, new object[] { path });
        }
        else
        {
          this.logTextBox.AppendText("FILE UPDATE: " + path);
          this.logTextBox.AppendText(Environment.NewLine);
        }
      }
    }

    private void dataModel_RepositoryAdded(HgRepository hgRepository)
    {
      //
      // TODO: Add folder monitor
      //
      this.logTextBox.AppendText("Repository Added: " + hgRepository.WorkingDirectory);
      this.logTextBox.AppendText(Environment.NewLine);

      //
      // TODO: Need to store the FSM in memory so it can be removed.
      //
      HgRepositoryFileSystemMonitor hgRepositoryFileSystemMonitor = new HgRepositoryFileSystemMonitor();
      hgRepositoryFileSystemMonitor.Repository = hgRepository;
      hgRepositoryFileSystemMonitor.FileIndexUpdate += new HgRepositoryFileSystemMonitor.FileIndexEvent(hgRepositoryFileSystemMonitor_FileIndexUpdate);

      //
    }

    private void dataModel_RepositoryRemoved(HgRepository hgRepository)
    {
      this.logTextBox.AppendText("Repository Removed: " + hgRepository.WorkingDirectory);
      this.logTextBox.AppendText(Environment.NewLine);

      //
      // TODO: Remove folder monitor
      //

    }

    private void SetupFolderMonitors()
    {
    }
    private void SetupFolderMonitor(HgRepository hgRepository)
    {
    }

    private void mnuMainFileExit_Click(object sender, EventArgs e)
    {
      Application.Exit();
    }

    private void mnuMainHelpAbout_Click(object sender, EventArgs e)
    {
      MessageBox.Show(Application.ProductName + " Version " + Application.ProductVersion);
    }




    

    private void mnuMainViewInfo_Click(object sender, EventArgs e)
    {
      this.splitContainerInner.Panel2Collapsed = !this.splitContainerInner.Panel2Collapsed;
      this.mnuMainViewInfo.Checked = !this.splitContainerInner.Panel2Collapsed;
    }

    private void mnuMainViewLog_Click(object sender, EventArgs e)
    {
      this.splitContainerHorizontal.Panel2Collapsed = !this.splitContainerHorizontal.Panel2Collapsed;
      this.mnuMainViewLog.Checked = !this.splitContainerHorizontal.Panel2Collapsed;
    }

    private void mnuMainLogClear_Click(object sender, EventArgs e)
    {
      this.logTextBox.Clear();
    }

    #region Position/Size Saving/Loading events

    private void ApplicationForm_Resize(object sender, EventArgs e)
    {
      //Properties.Settings.Default.Upgrade();
      //Properties.Settings.Default.Reload();
      Properties.Settings.Default.ApplicationFormW = this.Size.Width;
      Properties.Settings.Default.ApplicationFormH = this.Size.Height;
      Properties.Settings.Default.Save();
    }

    private void ApplicationForm_Move(object sender, EventArgs e)
    {
      //Properties.Settings.Default.Upgrade();
      //Properties.Settings.Default.Reload();
      Properties.Settings.Default.ApplicationFormX = this.Location.X;
      Properties.Settings.Default.ApplicationFormY = this.Location.Y;
      Properties.Settings.Default.Save();
    }

    private void ApplicationForm_Load(object sender, EventArgs e)
    {
      //Properties.Settings.Default.Upgrade();
      //Properties.Settings.Default.Reload();
      // Check for default values.
      if (Properties.Settings.Default.ApplicationFormW == 0)
      {
        Properties.Settings.Default.ApplicationFormW = this.Size.Width;
      }
      if (Properties.Settings.Default.ApplicationFormH == 0)
      {
        Properties.Settings.Default.ApplicationFormH = this.Size.Height;
      }
      if (Properties.Settings.Default.ApplicationFormX == 0)
      {
        Properties.Settings.Default.ApplicationFormX = this.Location.X;
      }
      if (Properties.Settings.Default.ApplicationFormY == 0)
      {
        Properties.Settings.Default.ApplicationFormY = this.Location.Y;
      }

      //
      // TODO: Place the ApplicationForm in the middle of the screen if default location is (0,0)
      //

      this.Size = new System.Drawing.Size(
        Properties.Settings.Default.ApplicationFormW,
        Properties.Settings.Default.ApplicationFormH);
      this.Location = new System.Drawing.Point(
        Properties.Settings.Default.ApplicationFormX,
        Properties.Settings.Default.ApplicationFormY);

      Properties.Settings.Default.Save();
    }

    #endregion

    private void mnuMainToolsOptions_Click(object sender, EventArgs e)
    {
      //
      // TODO: Pass options to optionsForm
      //
      Forms.OptionsForm optionsForm = new Forms.OptionsForm();
      if (DialogResult.OK == optionsForm.ShowDialog(this))
      {
        //
        // TODO: Save options
        //
      }
    }

  }
}